
// Copyright (C) 2011 Luca Piccioni
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//  
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//  
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Xml.Serialization;

namespace Derm.Render
{
	/// <summary>
	/// The compilation context.
	/// </summary>
	/// <remarks>
	/// This class shall group all elements which influence the compilation process.
	/// </remarks>
	[XmlType("ShaderCompilerContext")]
	public class ShaderCompilerContext
	{
		#region Constructors

		/// <summary>
		/// Default compilation context.
		/// </summary>
		public ShaderCompilerContext() : this(RenderContext.sCurrentShadingGLVersion, null, null)
		{
		
		}

		/// <summary>
		/// Compilation context that specify GLSL version.
		/// </summary>
		public ShaderCompilerContext(RenderContext.GLSLVersion version) : this(version, null, null)
		{
			
		}

		/// <summary>
		/// Compilation context that specify GLSL version.
		/// </summary>
		public ShaderCompilerContext(RenderContext.GLSLVersion version, IEnumerable<string> defines) : this(version, defines, null)
		{
			
		}

		/// <summary>
		/// Compilation context that specify GLSL version.
		/// </summary>
		public ShaderCompilerContext(RenderContext.GLSLVersion version, IEnumerable<string> defines, IEnumerable<string> includes)
		{
			mShaderVersion = version;

			if (defines != null) {
				mDefines = new List<string>();
				mDefines.AddRange(defines);
				mDefines.Sort();
			}

			if (includes != null)
				mIncludes = new List<string>(includes);
		}

		#endregion

		#region Shading Language Version

		/// <summary>
		/// The shading language version used by compiler.
		/// </summary>
		[XmlElement("ShaderVersion")]
		public RenderContext.GLSLVersion ShaderVersion { get { return (mShaderVersion); } set { mShaderVersion = value; } }

		/// <summary>
		/// The shading language version used by compiler.
		/// </summary>
		private RenderContext.GLSLVersion mShaderVersion = RenderContext.sCurrentShadingGLVersion;

		#endregion

		#region Preprocessor Symbols

		/// <summary>
		/// Set a preprocessor symbol controlling the shader compilation.
		/// </summary>
		/// <param name="def">
		/// A <see cref="System.String"/> representing the symbol string. This string will be
		/// presented after a <i>#define</i> preprocessor directive.
		/// </param>
		public void DefineSymbol(string def)
		{
			if (def == null)
				throw new ArgumentNullException("def");

			string[] defTokens = def.Split(new char[] { ' ', '\t', '(' });

			if (defTokens.Length <= 0)
				throw new ArgumentException(String.Format("invalid value '{0}'", def), "def");
			if (IsDefinedSymbol(defTokens[0]) == true)
				throw new ArgumentException(String.Format("symbol '{0}' already defined", defTokens[0]), "def");

			// Set symbol
			mDefines.Add(def);
		}

		/// <summary>
		/// Check whether a specific preprocessor symbol is defined.
		/// </summary>
		/// <param name="symbol">
		/// A <see cref="System.String"/> representing the symbol string. Only the first token of the symbol
		/// has to be specified.
		/// </param>
		/// <returns>
		/// It returns a boolean value indicating whether <paramref name="symbol"/> is defined or not.
		/// </returns>
		/// <exception cref="ArgumentNullException">
		/// This exception is thrown if the parameter <paramref name="symbol"/> is null.
		/// </exception>
		public bool IsDefinedSymbol(string symbol)
		{
			if (symbol == null)
				throw new ArgumentNullException("symbol");

			return (mDefines.Exists(delegate (string define) { return (define.StartsWith(symbol)); }));
		}

		/// <summary>
		/// Remove a symbol definition.
		/// </summary>
		/// <param name="symbol">
		/// A <see cref="System.String"/> representing the symbol string. Only the first token of the symbol
		/// has to be specified.
		/// </param>
		/// <exception cref="ArgumentNullException">
		/// This exception is thrown if the parameter <paramref name="symbol"/> is null.
		/// </exception>
		/// <exception cref="ArgumentException">
		/// This exception is thrown if the parameter <paramref name="symbol"/> is not defined.
		/// </exception>
		public void RemoveSymbol(string symbol)
		{
			if (IsDefinedSymbol(symbol) == false)
				throw new ArgumentException("not defined", "symbol");

			mDefines.Remove(symbol);
		}

		/// <summary>
		/// Remove all symbols definition.
		/// </summary>
		public void RemoveSymbols()
		{
			mDefines.Clear();
		}

		/// <summary>
		/// #define preprocessor directives used for compilation.
		/// </summary>
		[XmlElement("PreprocessorSymbol")]
		public List<string> Defines
		{
			get { return (mDefines); }
			set
			{
				mDefines.Clear();
				mDefines.AddRange(value);
			}
		}

		/// <summary>
		/// #define preprocessor directives used for compilation.
		/// </summary>
		/// <remarks>
		/// <para>
		/// The list has to be sorted for being independent on symbols order. This is mandatory for
		/// managing shaders caching.
		/// </para>
		/// <para>
		/// This imply that the symbol
		/// </para>
		/// </remarks>
		private readonly List<string> mDefines = new List<string>();

		#endregion

		#region Preprocessor Include Paths

		/// <summary>
		/// Set the include paths.
		/// </summary>
		/// <param name="paths">
		/// An ordered string collection that specify the list of the paths used
		/// for resolving shader inclusion by usinh <i>#include</i> preprocessor directive.
		/// </param>
		/// <remarks>
		/// This method reset the entire include path used by compiler.
		/// </remarks>
		public void SetIncludePaths(IEnumerable<string> paths)
		{
			if (paths == null)
				throw new ArgumentNullException("paths");

			mIncludes.Clear();
			foreach (string path in paths) {
				if (String.IsNullOrEmpty(path) == true)
					throw new ArgumentException("invalid path", "paths");
				mIncludes.Add(path);
			}
		}

		/// <summary>
		/// Ordered paths for relative #include preprocessor directives.
		/// </summary>
		public ICollection<string> Includes { get { return (mIncludes.AsReadOnly()); } }

		/// <summary>
		/// Ordered paths for relative #include preprocessor directives.
		/// </summary>
		/// <remarks>
		/// 
		/// </remarks>
		private readonly List<string> mIncludes = new List<string>();

		#endregion
	}
}
